//
//  ApiError.swift
//  ZZFoundation
//
//  Created by xiaoyuan on 2021/8/3.
//

import Foundation
import Moya
import ObjectMapper
import Alamofire

/// 针对业务逻辑实现的接口请求错误枚举
public enum ApiError: Error {
    // 内部错误，用于接口请求成功时，后台反馈的接口参数错误信息（比如登录时密码错误、上传表单时缺少必要字段等），而非服务器或者网络异常错误
    // 这些错误，服务器通常会给回一些message，我们可能要通过`Toast`提醒用户
    case `internal`(code: Int?, message: String?)
    // 服务器错误，可能是无法连接到服务器，无网络请求、json解析等
    case service(error: Error)
    // 未知错误
    case unknow
    
    public var title: String {
        switch self {
        case .`internal`(let code, let message):
            return message ?? "未知错误(code=\(code ?? 0))"
        case .service(let error):
            debugLog(error.localizedDescription)
            let error = error as? MoyaError
            if let body = try? error?.response?.mapJSON() as? [String: Any],
                let errorResponse = Mapper<ErrorResponse>().map(JSON: body),
                let message = errorResponse.message, message.count > 0 {
                return message
            }
            else {
                return error?.localizedDescription ?? "网络异常"
            }
        case .unknow:
            return "未知异常，请稍后再试！"
        }
    }
    
    public var code: Int? {
        switch self {
        case .internal(let code, _):
            return code
        case .service(let error):
            if case let .underlying(error, _) = (error as? MoyaError) {
                // 适配Alamofire 5.0
                if let error = error as? AFError {
                    return error.responseCode
                }
                if (error as? AFError)?.isExplicitlyCancelledError == true {
                    return NSURLErrorCancelled
                }
            }
            return ((error as? MoyaError)?.errorUserInfo[NSUnderlyingErrorKey] as? NSError)?.code ?? NSURLErrorUnknown
        case .unknow:
            return 99999
        }
    }
}

extension ApiError: LocalizedError {
    /// 自定义`Error.localizedDescription`返回值
    public var errorDescription: String? {
        return title
    }
}

public struct ErrorResponse: Mappable {
    public init?(map: Map) {}
    
    public var code: Int?
    public var message: String?
    public var url: String?

    public init(code: Int?, message: String?, url: String? = nil) {
        self.code = code
        self.message = message
        self.url = url
    }
    
    public mutating func mapping(map: Map) {
        code <- map["code"]
        code <- map["status"]
        message <- map["msg"]
        message <- map["message"]
        message <- map["error"]
        url <- map["path"]
    }
}
